#include "client_simulator/rtsp_client.h"
#include <fstream>

static void CloseTimerCallback(evutil_socket_t fd, int16_t flags, void *ctx) {
  RtspClient::Ptr live_this = ((RtspClient*)ctx)->shared_from_this();
  live_this->OnCloseTimer();
}

RtspClient::RtspClient(EventLoop::Ptr event_loop, const std::string &sn)
  : event_loop_(event_loop)
  , sn_(sn)
  , async_socket_(NULL) {
}


RtspClient::~RtspClient() {
}

bool RtspClient::Start(const std::string &ip, int port) {
  async_socket_ = AsyncSocket::Ptr(new AsyncSocket(event_loop_));

  async_socket_->SignalConnected.connect(
    this, &RtspClient::OnAsyncSocketConnected);
  async_socket_->SignalPacket.connect(
    this, &RtspClient::OnAsyncSocketPacket);
  async_socket_->SignalError.connect(
    this, &RtspClient::OnAsyncSocketError);
  async_socket_->SignalClosed.connect(
    this, &RtspClient::OnAsyncSocketClosed);

  if (!async_socket_->Initialize()) {
    return false;
  }
  return async_socket_->AsyncConnect(ip, port);
}

void RtspClient::Play(int duration_seconds) {
  async_socket_->AsyncSend(CMD_START_PLAY, NULL, 0);
  struct timeval close_tvtimer;
  close_tvtimer.tv_sec = duration_seconds;
  close_tvtimer.tv_usec = 0;
  close_evtimer_ = evtimer_new(event_loop_->GetEventBase(),
                               CloseTimerCallback, this);
  event_add(close_evtimer_, &close_tvtimer);
}

bool RtspClient::Save(const char* path) {
  std::string filepath = std::string(path) + "\\" + sn_;

  std::ofstream ofs;
  ofs.open(filepath.c_str(), std::ios::trunc | std::ios::out);
  if (!ofs.is_open()) {
    fprintf(stderr, "Cannot create file: %s!\n", filepath.c_str());
    return false;
  }

  for (auto iter = delays_.begin(); iter != delays_.end(); ++iter) {
    ofs << *iter << std::endl;
  }

  ofs.close();
  return true;
}

void RtspClient::OnCloseTimer() {
  SignalClosed(shared_from_this());
}

void RtspClient::OnAsyncSocketConnected(AsyncSocket::Ptr async_socket) {
  assert(async_socket == async_socket_);

  evutil_gettimeofday(&base_tv_, NULL);
  struct timeval tv = HTON_TIMEVAL(base_tv_);
  async_socket_->AsyncSend(CMD_HELLO, (const char*)&tv, sizeof(tv));
}

void RtspClient::OnAsyncSocketPacket(AsyncSocket::Ptr async_socket,
                                     uint16_t cmd,
                                     const char *data,
                                     uint32_t data_size) {
  assert(async_socket == async_socket_);

  switch (cmd) {
  case CMD_HELLO_ACK: {
    struct timeval during_tv;
    memcpy(&during_tv, data, sizeof(during_tv));
    during_tv = NTOH_TIMEVAL(during_tv);

    struct timeval base_delay_tv = CalculateDelay(during_tv);
    base_delay_usec_ = CALCULATE_USEC(base_delay_tv) / 2;

    SignalHello();
    break;
  }
  case CMD_VIDEO_FRAME_DATA: {
    struct timeval during_tv;
    memcpy(&during_tv, data, sizeof(during_tv));
    during_tv = NTOH_TIMEVAL(during_tv);

    struct timeval video_delay_tv = CalculateDelay(during_tv);
    int64_t video_delay_usec_ =
      CALCULATE_USEC(video_delay_tv) - base_delay_usec_;

    delays_.push_back(video_delay_usec_);
  }
  }
}

void RtspClient::OnAsyncSocketError(AsyncSocket::Ptr async_socket,
                                    int error_code) {
  assert(async_socket == async_socket_);
  SignalClosed(shared_from_this());
}

void RtspClient::OnAsyncSocketClosed(AsyncSocket::Ptr async_socket) {
  assert(async_socket == async_socket_);
  SignalClosed(shared_from_this());
}

struct timeval RtspClient::CalculateDelay(struct timeval &during_tv) {
  struct timeval now_tv;
  evutil_gettimeofday(&now_tv, NULL);

  struct timeval delay_tv;
  evutil_timersub(&now_tv, &base_tv_, &delay_tv);
  evutil_timersub(&delay_tv, &during_tv, &delay_tv);
  return delay_tv;
}
